//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/builtins/builtins.h"
#include "sagittarius/session/session.h"
#include "sagittarius/io/io.h"
#include <string.h>
#include <malloc.h>


#if 0
const char* type_builtinstr[SG_BUILTIN_TYPE_END] = {
    "unset",
    "empty",
    "ref",
    "int",
    "real",
    "str",
    //"empty",
    "nil"
};

const int type_builtinvis[SG_BUILTIN_TYPE_END] = {
    0,
    1,
    0,
    0,
    0,
    0,
    //"empty",
    1
};
#endif
sg_type_dtable_val* make_empty_dtable(builtin_type_ctx* bt) {
    sg_type_dtable_val* d = malloc(sizeof(sg_type_dtable_val));
    d->t = (sg_type*)bt->type_dtable;
    AN(d->t);
    d->tt = NULL;
    d->n = 0;
    d->m = 0;
    return d;
}

sg_type_dtable_val* copy_dtable(sg_type_dtable_val* src) {
    sg_type_dtable_val* dst = malloc(sizeof(sg_type_dtable_val));
    dst->t = src->t;
    dst->tt = malloc(src->c*sizeof(sg_type_dtran_val*));
    memcpy(dst->tt, src->tt, src->n*sizeof(sg_type_dtran_val*));
    dst->n = src->n;
    dst->c = src->c;
    return dst;
}

sg_type_dtable_val* copy_center_dtable(sg_type_dtable_val* src, sg_hc* hc) {
    uint64 i;
    
    sg_type_dtable_val* dst = malloc(sizeof(sg_type_dtable_val));
    dst->t = src->t;
    dst->m = src->m;
    
    if(src->tt) {
        dst->tt = malloc(src->c*sizeof(sg_type_dtran_val*));
        for(i=0;i<src->n;++i) {
            dst->tt[i] = copy_dtran_value(src->tt[i]);
            dst->tt[i]->dst = hc;
        }
        dst->n = src->n;
        dst->c = src->c;
    } else {
        dst->tt = NULL;
        dst->n = 0;
    }
    return dst;
}

sg_type_dtran_val* copy_dtran_value(sg_type_dtran_val* src) {
    sg_type_dtran_val* dst = malloc(sizeof(sg_type_dtran_val));
    // NOTE: does not deep copy fields, etc
    memcpy(dst,src,sizeof(sg_type_dtran_val));
    return dst;
}

sg_type_dtran_val* dtable_new_dtran(sg_type_dtable_val* dt, symcode s, sg_hc* dst) {
    sg_type_dtran_val* v=malloc(sizeof(sg_name_val));
    v->t = (sg_type*)(sg_global_session()->bt->type_dtable);
    v->s = s;
    //v->o = dt->n*sizeof(sg_value*);
    v->o = dt->m;
    v->dst = dst;
    return v;
}

/// Get field at offset
sg_value* sg_get_fieldr(sg_value* v, uint64 o) {
    sg_value** pv = v->p;
    return pv[o];
}

/// Get lval at offset
sg_value** sg_get_fieldl(sg_value* v, uint64 o) {
    sg_value** pv = v->p;
    return pv + o;
}

// also inserts if it does not exist
sg_type_dtran_val* sg_hc_get_tran(sg_hc* src, symcode s) {
    uint64 i;
    sg_type_dtable_val* dt=src->dt;
    sg_type_dtran_val* dv;
    AT(dt->t == (sg_type*)sg_global_session()->bt->type_dtable, "Not a dtable");
    SG_WRITE(2, "  sg_hc_get_tran dt");
    sg_writePtr(2, dt);
    SG_WRITE(2, ", n = ");
    sg_writeUInt64(2, dt->n);
    SG_WRITE(2, "\n");
    for(i=0;i<dt->n;++i) {
        SG_WRITE(2, "  sg_hc_get_tran candidate \"");
        symtabPrintSymK(2, sg_global_session()->st, dt->tt[i]->s);
        SG_WRITE(2, "\"\n");
        if(dt->tt[i]->s == s) {
            SG_WRITE(2, "  symbol found\n");
            return dt->tt[i];
        }
    }
    // does not exist - insert transition
    dv = dtable_push_tran(dt, dtable_new_dtran(dt, s, malloc(sizeof(sg_hc))));
    // copy current type
    sg_copy_hc(dv->dst, src);
    ++dv->dst->dt->m;
    // install field in new type
    //dtable_push_tran(dv->dst->dt, dtable_new_dtran(dv->dst->dt, s, dv->dst));
    return dv;
}
// sg_type_dtran_val* sg_hc_tran_auto(sg_hc* src, symcode s) {
//     uint64 i;
//     sg_type_dtable_val* dt=src->dt;
//     sg_type_dtran_val* dv;
//     AN(dt->t);
//     AN(sg_global_session()->bt->type_dtable);
//     AT(dt->t == (sg_type*)sg_global_session()->bt->type_dtable, "Not a dtable");
//     SG_WRITE(2, "  sg_hc_tran_auto dt");
//     sg_writePtr(2, dt);
//     SG_WRITE(2, ", n = ");
//     sg_writeUInt64(2, dt->n);
//     SG_WRITE(2, "\n");
//     for(i=0;i<dt->n;++i) {
//         SG_WRITE(2, "  sg_hc_tran_auto candidate \"");
//         symtabPrintSymK(2, sg_global_session()->st, dt->tt[i]->s);
//         SG_WRITE(2, "\"\n");
//         if(dt->tt[i]->s == s)
//             return dt->tt[i];
//     }
//     // insert transition
//     dv = dtable_push_tran(dt, dtable_new_dtran(dt, s, src));
//     return dv;
// }

void dtable_grow(sg_type_dtable_val* dt) {
    if(dt->tt) {
        sg_type_dtran_val** tt;
        dt->c <<= 2;
        tt = malloc(dt->c*sizeof(sg_type_dtran_val*));
        memcpy(tt, dt->tt, dt->n*sizeof(sg_type_dtran_val*));
        free(dt->tt);
        dt->tt = tt;
    } else {
        dt->c = 4;
        dt->tt = malloc(dt->c*sizeof(sg_type_dtran_val*));
        dt->n = 0;
    }
}

sg_type_dtran_val* dtable_push_tran(sg_type_dtable_val* dt, sg_type_dtran_val* t) {
    SG_WRITE(2, "dtable_push_tran\n");
    if(!dt->tt || dt->n == dt->c)
        dtable_grow(dt);
    dt->tt[dt->n++] = t;
    SG_WRITE(2, "  n = ");
    sg_writeUInt64(2, dt->n);
    SG_WRITE(2, "\n");
    return t;
}

void release_dtable(sg_type_dtable_val* d) {
    uint64 i;
    if(d->tt) {
        for(i=0;i<d->n;++i)
            free(d->tt[i]);
        free(d->tt);
    }
    free(d);
}

// -- Fields --

sg_fieldrec_val* sg_make_static_fun_field(sg_fun_val* fn) {
    sg_fieldrec_val* r = malloc(sizeof(sg_fieldrec_val));
    r->s = fn->s;
    r->v = (sg_value*)fn;
    r->k=0;
    return r;
}

void sg_fieldtab_grow(sg_fieldtab_val* ft) {
    if(ft->f) {
        sg_fieldrec_val** f;
        ft->c <<= 2;
        f = malloc(ft->c*sizeof(sg_fieldrec_val*));
        memcpy(f, ft->f, ft->n*sizeof(sg_fieldrec_val*));
        free(ft->f);
        ft->f = f;
    } else {
        ft->c = 4;
        ft->f = malloc(ft->c*sizeof(sg_fieldrec_val*));
        ft->n = 0;
    }
}

sg_fieldrec_val* sg_push_field(sg_fieldtab_val* ft, sg_fieldrec_val* r) {
     if(!ft->f || ft->n == ft->c)
        sg_fieldtab_grow(ft);
    ft->f[ft->n++] = r;
    return r;
}

// void sg_tp_push_static_fun(sg_type* t, sg_fun_val* fn) {
//     sg_fieldtab_val* ft=t->f;
//     sg_fieldrec_val* r=sg_make_static_fun_field(fn);
//     sg_push_field(ft, r);
// }

sg_fieldrec_val* sg_lookup_field(sg_fieldtab_val* ft, symcode s) {
    uint64 i;
    for(i=0;i<ft->n;++i) {
        if(ft->f[i]->s == s)
            return ft->f[i];
    }
    // Not found
    return NULL;
}

// -- HC --

void sg_init_hc(sg_hc* hc, builtin_type_ctx* bt) {
    hc->dt = make_empty_dtable(bt);
    hc->o = 0;
}

// -- Types --

void init_sg_tp(sg_type* t, builtin_type_ctx* bt) {
    // nothing to do?
}

void sg_copy_hc(sg_hc* dst, sg_hc* src) {
    dst->dt = copy_center_dtable(src->dt, dst);
    dst->o = src->o; // correct?
}

void release_sg_tp(sg_type* t) {
    free(t);
}

builtin_type_ctx* create_builtin_type_ctx() {
    builtin_type_ctx* bc = malloc(sizeof(builtin_type_ctx));
    
    bc->type_dtable = malloc(sizeof(sg_type_dtable_tp));
    bc->type_dtable->t = (sg_type*)bc->type_dtable;
    bc->type_dtable->sz = sizeof(sg_type_dtable_val);
    init_sg_tp((sg_type*)bc->type_dtable, bc);
    
    bc->none = malloc(sizeof(sg_type));
    bc->none->t = (sg_type*)bc->none;
    bc->none->sz = sizeof(sg_value);
    init_sg_tp((sg_type*)bc->none, bc);
    
    bc->ref = malloc(sizeof(sg_type));
    bc->ref->t = (sg_type*)bc->ref;
    bc->ref->sz = sizeof(sg_value);
    init_sg_tp((sg_type*)bc->ref, bc);
    
    bc->tp = malloc(sizeof(sg_type_tp));
    bc->tp->t = (sg_type*)bc->tp;
    bc->tp->sz = sizeof(sg_type_val);
    init_sg_tp((sg_type*)bc->tp, bc);
    
    bc->emp = malloc(sizeof(sg_type));
    bc->emp->t = bc->emp;
    bc->emp->sz = sizeof(sg_value);
    init_sg_tp(bc->emp, bc);
    
    bc->it = malloc(sizeof(sg_int_tp));
    bc->it->t = (sg_type*)bc->it;
    bc->it->sz = sizeof(sg_int_val);
    init_sg_tp((sg_type*)bc->it, bc);
    
    bc->real = malloc(sizeof(sg_real_tp));
    bc->real->t = (sg_type*)bc->real;
    bc->real->sz = sizeof(sg_real_val);
    init_sg_tp((sg_type*)bc->real, bc);
    
    bc->complex = malloc(sizeof(sg_complex_tp));
    bc->complex->t = (sg_type*)bc->complex;
    bc->complex->sz = 0; // FIXME
    init_sg_tp((sg_type*)bc->complex, bc);
    
    bc->str = malloc(sizeof(sg_str_tp));
    bc->str->t = (sg_type*)bc->str;
    bc->str->sz = sizeof(sg_str_val);
    init_sg_tp((sg_type*)bc->str, bc);
    
    bc->nil = malloc(sizeof(sg_type));
    bc->nil->t = (sg_type*)bc->nil;
    bc->nil->sz = sizeof(sg_value);
    init_sg_tp((sg_type*)bc->nil, bc);
    
    bc->fun = malloc(sizeof(sg_fun_tp));
    bc->fun->t = (sg_type*)bc->fun;
    bc->fun->sz = sizeof(sg_fun_val);
    init_sg_tp((sg_type*)bc->fun, bc);
    
    bc->mir = malloc(sizeof(sg_mir_tp));
    bc->mir->t = (sg_type*)bc->mir;
    bc->mir->sz = sizeof(sg_mir_val);
    init_sg_tp((sg_type*)bc->mir, bc);
    
    bc->name = malloc(sizeof(sg_name_tp));
    bc->name->t = (sg_type*)bc->name;
    bc->name->sz = sizeof(sg_name_val);
    init_sg_tp((sg_type*)bc->name, bc);
    
    bc->syminst = malloc(sizeof(sg_symbol_inst_tp));
    bc->syminst->t = (sg_type*)bc->syminst;
    bc->syminst->sz = sizeof(sg_symbol_inst);
    init_sg_tp((sg_type*)bc->syminst, bc);
    
    bc->fctx = malloc(sizeof(sg_fctx_tp));
    bc->fctx->t = (sg_type*)bc->fctx;
    bc->fctx->sz = sizeof(sg_fctx_val);
    init_sg_tp((sg_type*)bc->fctx, bc);
    
    bc->fieldrec = malloc(sizeof(sg_fieldrec_tp));
    bc->fieldrec->t = (sg_type*)bc->fieldrec;
    bc->fieldrec->sz = sizeof(sg_fieldrec_val);
    init_sg_tp((sg_type*)bc->fieldrec, bc);
    
    bc->fieldtab = malloc(sizeof(sg_fieldtab_tp));
    bc->fieldtab->t = (sg_type*)bc->fieldtab;
    bc->fieldtab->sz = sizeof(sg_fieldtab_val);
    init_sg_tp((sg_type*)bc->fieldtab, bc);
    
    bc->type_dtran = malloc(sizeof(sg_type_dtable_tp));
    bc->type_dtran->t = (sg_type*)bc->type_dtran;
    bc->type_dtran->sz = sizeof(sg_type_dtran_val);
    init_sg_tp((sg_type*)bc->type_dtran, bc);
    
    /// module
    bc->mod = malloc(sizeof(sg_mod_type));
    bc->mod->t = (sg_type*)bc->mod;
    bc->mod->sz = sizeof(sg_module);
    init_sg_tp((sg_type*)bc->mod, bc);
    
    /// mod loader
    bc->modld = malloc(sizeof(sg_mod_loader_type));
    bc->modld->t = (sg_type*)bc->modld;
    bc->modld->sz = sizeof(sg_mod_loader);
    init_sg_tp((sg_type*)bc->modld, bc);
    
    /// list
    bc->list = malloc(sizeof(sg_list_tp));
    bc->list->t = (sg_type*)bc->list;
    bc->list->sz = sizeof(sg_list_val);
    init_sg_tp((sg_type*)bc->list, bc);
    
    /// list_iter
    bc->list_iter = malloc(sizeof(sg_list_iter_tp));
    bc->list_iter->t = (sg_type*)bc->list_iter;
    bc->list_iter->sz = sizeof(sg_list_iter_val);
    init_sg_tp((sg_type*)bc->list_iter, bc);
    
    /// locals
    bc->locals = malloc(sizeof(sg_locals_tp));
    bc->locals->t = (sg_type*)bc->locals;
    bc->locals->sz = sizeof(sg_locals_val);
    init_sg_tp((sg_type*)bc->locals, bc);
    
    /// node
    bc->node = malloc(sizeof(sg_node_tp));
    bc->node->t = (sg_type*)bc->node;
    bc->node->sz = sizeof(sg_node_val);
    init_sg_tp((sg_type*)bc->node, bc);
    
    /// rxn_rule
    bc->rxn_rule = malloc(sizeof(sg_reaction_rule_tp));
    bc->rxn_rule->t = (sg_type*)bc->rxn_rule;
    bc->rxn_rule->sz = sizeof(sg_reaction_rule_val);
    init_sg_tp((sg_type*)bc->rxn_rule, bc);
    
    /// network
    bc->network = malloc(sizeof(sg_network_tp));
    bc->network->t = (sg_type*)bc->network;
    bc->network->sz = sizeof(sg_network_val);
    init_sg_tp((sg_type*)bc->network, bc);
    
    // empty hidden class
    bc->hc_base = malloc(sizeof(sg_hc));
    sg_init_hc(bc->hc_base, bc);
    
    // object hidden class
    bc->hc_obj = malloc(sizeof(sg_hc));
    sg_copy_hc(bc->hc_obj, bc->hc_base);
    
    return bc;
}

/// init_type_table
void install_builtin_syms(symtab* st, builtin_type_ctx* bc) {
    LexBuf w;
    /*w = sg_lex_str2buf("empty");
    bc->emp->s = symtab_get_or_insertl(st, w, LX_BUFFER_LEN(w)-1).k;
    LX_FREE_BUFFER(w);*/
    w = sg_lex_str2buf("nil");
    bc->nil->s = symtab_get_or_insertl(st, w, LX_BUFFER_LEN(w)-1).k;
    LX_FREE_BUFFER(w);
}

void release_builtin_type_ctx(builtin_type_ctx* bc) {
    release_sg_tp(bc->emp);
    release_sg_tp((sg_type*)bc->it);
    release_sg_tp((sg_type*)bc->str);
    release_sg_tp((sg_type*)bc->nil);
    release_sg_tp((sg_type*)bc->fun);
    free(bc);
}

void dump_type_repr(int fd, sg_type* t, symtab* st) {
    // TODO: if symtab-based repr then this
    symtabPrintSymK(fd, st, t->s);
    // else use internal string
}

sg_value* sg_shallow_copy(sg_value* v) {
    // TODO: squeeze these mallocs into one
    sg_value* u = malloc(v->t->sz);
    memcpy(u, v, v->t->sz);
    u->p = malloc(v->hc->o*sizeof(sg_value*));
    memcpy(u->p, v->p, v->hc->o*sizeof(sg_value*));
    return u;
}

void sg_init_val(sg_value* v) {
    //v->hc = sg_global_session()->bt->hc_obj;
    // need to copy hc obj prototype for all objects
    sg_instantiate_proto(v, sg_global_session()->pro->obj);
    v->r = 0;
}

void sg_val_incref(sg_value* v) {
    ++v->r;
}

void sg_val_decref(sg_value* v) {
    if(--v->r == 0) {
        SG_WRITE(2, "Destroying object");
        //run destructors for types that have them
        if(v->t == (sg_type*)sg_global_session()->bt->locals)
            sg_release_locals((sg_locals_val*)v);
        else if(v->t == (sg_type*)sg_global_session()->bt->list)
            sg_release_list((sg_list_val*)v);
        else
            free(v);
    }
}

sg_value* sg_create_int(int64 x) {
    sg_int_val* v = malloc(sg_global_session()->bt->it->sz);
    sg_init_val((sg_value*)v);
    v->t = (sg_type*)sg_global_session()->bt->it;
    v->v = x;
    return (sg_value*)v;
}

sg_value* sg_create_uint(uint64 x) {
    sg_int_val* v = malloc(sg_global_session()->bt->it->sz);
    sg_init_val((sg_value*)v);
    // set type
    v->t = (sg_type*)sg_global_session()->bt->it;
    v->v = (int64)x;
    return (sg_value*)v;
}

uint64 sg_check_int(sg_int_val* i) {
    AT(i->t == (sg_type*)sg_global_session()->bt->it, "Not an int");
    if(i->t == (sg_type*)sg_global_session()->bt->it)
        return 1;
    else
        return 0;
}

sg_int_val* sg_cast_int(sg_value* v) {
    sg_int_val* i = (sg_int_val*)v;
    sg_check_int(i);
    return i;
}

uint64 sg_check_real(sg_real_val* r) {
    AT(r->t == (sg_type*)sg_global_session()->bt->real, "Not a real");
    if(r->t == (sg_type*)sg_global_session()->bt->real)
        return 1;
    else
        return 0;
}

sg_real_val* sg_cast_to_real(sg_value* v) {
    sg_real_val* r = (sg_real_val*)v;
    sg_check_real(r);
    return r;
}

// -- str --

sg_value* sg_create_str(void* _b, uint64 n) {
    constLexBuf b = (constLexBuf)_b;
    sg_str_val* v = malloc(sg_global_session()->bt->str->sz);
    sg_init_val((sg_value*)v);
    
    // set type
    v->t = (sg_type*)sg_global_session()->bt->str;
    
    // init data
    v->w = b;
    v->n = n;
    return (sg_value*)v;
}

//TODO: sg_release_str

uint64 sg_check_str(sg_str_val* str) {
    AT(str->t == (sg_type*)sg_global_session()->bt->str, "Not a string");
    if(str->t == (sg_type*)sg_global_session()->bt->str) // used to be bt->fun; why?
        return 1;
    else
        return 0;
}

sg_str_val* sg_cast_str(sg_value* v) {
    sg_str_val* str = (sg_str_val*)v;
    sg_check_str(str);
    return str;
}

// -- fun --

sg_fun_val* sg_create_fun() {
    sg_fun_val* f=malloc(sizeof(sg_fun_val));
    f->t = (sg_type*)sg_global_session()->bt->fun;
    return f;
}

void sg_release_fun(sg_fun_val* f) {
    free(f);
}

uint64 sg_check_fun_val(sg_fun_val* f) {
    AT(f->t == (sg_type*)sg_global_session()->bt->fun, "Not a funval");
    if(f->t == (sg_type*)sg_global_session()->bt->fun)
        return 1;
    else
        return 0;
}

sg_fun_val* sg_cast_fun_val(sg_value* v) {
    sg_fun_val* f = (sg_fun_val*)v;
    sg_check_fun_val(f);
    return f;
}

void sg_rt_call_check(sg_fun_val* f, uint64 nargs) {
    SG_WRITE(2, "sg_rt_call_check\n");
    sg_check_fun_val(f);
    AT(f->nargs == nargs, "Wrong number of arguments");
}

sg_standard_protos* create_builtin_protos(builtin_type_ctx* bt, builtin_fun_ctx* bf) {
    sg_value* v, **ppval;
    
    // prototypes
    sg_standard_protos* pro = malloc(sizeof(sg_standard_protos));
    
    // symbols
    symcode s_tostr = symtab_get_or_insert_cstr(sg_global_session()->st, "tostr");
    
    // -- obj --
    // object
    v = malloc(sg_global_session()->bt->emp->sz);
    // type
    v->t = sg_global_session()->bt->emp;
    // hc
    v->hc = sg_global_session()->bt->hc_obj;
    // fields
    v->p = NULL;
    ppval = bf_do_faccessl(v, s_tostr);
    *ppval = (sg_value*)bf->tostr;
    // set
    pro->obj = v;
    
    return pro;
}

uint64 sg_value_n_fields(sg_value* v) {
    AN(v->hc, "No HC");
    AN(v->hc->dt, "No HC DT");
    return v->hc->dt->m;
}

void sg_instantiate_proto(sg_value* dst, sg_value* src) {
    SG_WRITE(2, "sg_instantiate_proto\n");
    uint64 n;
    dst->hc = src->hc;
    
    n=sg_value_n_fields(dst);
    AT(sg_value_n_fields(src) == n, "Field number mismatch");
    
    SG_WRITE(2, "sg_instantiate_proto n = ");
    sg_writeUInt64(2, n);
    SG_WRITE(2, "\n");
    
    if(n) {
        dst->p = malloc(n*sizeof(sg_value*));
        memcpy(dst->p, src->p, n*sizeof(sg_value*));
    } else {
        dst->p = NULL;
    }
}

// ** list **

// create
sg_list_val* sg_create_list() {
    sg_list_val* list = malloc(sizeof(sg_list_val));
    list->t = (sg_type*)sg_global_session()->bt->list;
    
    list->v = NULL;
    list->n = list->c = 0;
    
    return list;
}

// release
void sg_release_list(sg_list_val* list) {
    AT(list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    uint64 i;
    for(i=0;i<list->n;++i) {
        sg_val_decref(list->v[i]);
    }
}

// grow
void sg_list_grow(sg_list_val* list, uint64 cc) {
    AT(list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    AT(cc >= list->c, "Must not decrease size of list");
    
    if(list->v) {
        sg_value** v = malloc(cc*sizeof(sg_value*));
        
        memcpy(v, list->v, list->n*sizeof(sg_value*));
        // no net change of ref count for objects in the list
        free(list->v);
        
        list->v = v;
        list->c = cc;
    } else {
        list->n = 0;
        list->c = cc;
        list->v = malloc(list->c*sizeof(sg_value*));
    }
    
    SG_WRITE(2, "grew list to ");
    sg_writeUInt64(2, list->c);
    SG_WRITE(2, "\n");
}

// append
void sg_list_append(sg_list_val* list, sg_value* obj) {
    if(list->n == list->c) {
        uint64 c=list->c;
        if(!c)
            c = 1;
        else
            c <<= 1;
        sg_list_grow(list, c);
    }
    AT(list->n <= list->c, "Overflow");
    
    list->v[list->n++] = obj;
}

// reserve
void sg_list_reserve(sg_list_val* list, uint64 n) {
    uint64 c;
    AT(list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    
    c=1;
    while(c<n)
        c <<= 1;
    if(c > list->c)
        sg_list_grow(list, c);
    list->n = n;
}

// get by index
sg_value* sg_list_get_by_index(sg_list_val* list, uint64 i) {
    AT(list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    AT(i < list->n, "Index out of range");
    
    return list->v[i];
}

// store by index
void sg_list_store_by_index(sg_list_val* list, sg_value* obj, uint64 i) {
    AT(list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    if(i >= list->n) {
        SG_WRITE(2, "i = ");
        sg_writeUInt64(2, i);
        SG_WRITE(2, "\n");
    }
    AT(i < list->n, "Index out of range");
    
    list->v[i] = obj;
}

// ** list iterators **

void sg_release_list_iter(sg_list_iter_val* iter) {
    AT(iter->t == (sg_type*)sg_global_session()->bt->list_iter, "Wrong type for list iter");
}

sg_list_iter_val* sg_list_get_iter(sg_list_val* list) {
    sg_list_iter_val* iter = malloc(sizeof(sg_list_iter_val));
    AT(list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    
    sg_init_val((sg_value*)iter);
    iter->t = (sg_type*)sg_global_session()->bt->list_iter;
    
    iter->list = list;
    iter->n = 0;
    
    return iter;
}

void sg_list_iter_next(sg_list_iter_val* iter) {
    AT(iter->t == (sg_type*)sg_global_session()->bt->list_iter, "Wrong type for list iter");
    
    ++iter->n;
}

sg_value* sg_list_iter_deref(sg_list_iter_val* iter) {
    AT(iter->t == (sg_type*)sg_global_session()->bt->list_iter, "Wrong type for list iter");
    AT(iter->list->t == (sg_type*)sg_global_session()->bt->list, "Wrong type for list");
    
    if(iter->n < iter->list->n)
        return sg_list_get_by_index(iter->list, iter->n);
    else
        return NULL;
}

// ** locals **

sg_locals_val* sg_create_locals(sg_locals_val* parent) {
    sg_locals_val* locals = malloc(sizeof(sg_locals_val));
    locals->t = (sg_type*)sg_global_session()->bt->locals;
    
    // parent
    locals->par = parent;
    if(locals->par)
        sg_val_incref((sg_value*)locals->par);
    
    // list of vars
    locals->vars = sg_create_list();
    sg_val_incref((sg_value*)locals->vars);
    
    return locals;
}

// get parent
sg_locals_val* sg_locals_get_parent(sg_locals_val* locals) {
    AT(locals->t == (sg_type*)sg_global_session()->bt->locals, "Wrong type for locals");
    AN(locals->par, "No parent");
    return locals->par;
}

// get object by index
sg_value* sg_locals_get_obj_by_index(sg_locals_val* locals, uint64 i) {
    AT(locals->t == (sg_type*)sg_global_session()->bt->locals, "Wrong type for locals");
    AT(i < locals->vars->n, "Locals overrun");
    
    return sg_cast_as_symbol_inst(sg_list_get_by_index(locals->vars, i))->o;
}

// get symbol by index
sg_symbol_inst* sg_locals_get_sym_by_index(sg_locals_val* locals, uint64 i) {
    AT(locals->t == (sg_type*)sg_global_session()->bt->locals, "Wrong type for locals");
    AT(i < locals->vars->n, "Locals overrun");
    
    return sg_cast_as_symbol_inst(sg_list_get_by_index(locals->vars, i));
}

// store by index
void sg_locals_store_by_index(sg_locals_val* locals, sg_value* obj, uint64 i) {
    sg_symbol_inst* symi;
    AT(locals->t == (sg_type*)sg_global_session()->bt->locals, "Wrong type for locals");
    AT(i < locals->vars->n, "Locals overrun");
    
    symi = sg_locals_get_sym_by_index(locals, i);
    AT(symi->t == (sg_type*)sg_global_session()->bt->syminst, "Unexpected type for syminst");
    
    symi->o = obj;
}

void sg_release_locals(sg_locals_val* locals) {
    AT(locals->t == (sg_type*)sg_global_session()->bt->locals, "Wrong type for locals");
    sg_val_decref((sg_value*)locals->vars);
    free(locals);
}

// ** symbol instance **

sg_symbol_inst* sg_create_symbol_inst() {
    sg_symbol_inst* syminst = malloc(sizeof(sg_symbol_inst));
    syminst->t = (sg_type*)sg_global_session()->bt->syminst;
    
    return syminst;
}

sg_symbol_inst* sg_create_symbol_inst_s(symcode s) {
    sg_symbol_inst* syminst = malloc(sizeof(sg_symbol_inst));
    syminst->t = (sg_type*)sg_global_session()->bt->syminst;
    
    syminst->s = s;
    syminst->o = NULL;
    
    return syminst;
}

sg_symbol_inst* sg_cast_as_symbol_inst(sg_value* obj) {
    SG_WRITE(2, "obj->t = ");
    sg_writePtr(2, obj->t);
    SG_WRITE(2, ", syminst = ");
    sg_writePtr(2, sg_global_session()->bt->syminst);
    SG_WRITE(2, "\n");
    AT(obj->t == (sg_type*)sg_global_session()->bt->syminst, "Wrong type: cannot cast to symbol instance");
    return (sg_symbol_inst*)obj;
}

void sg_bind_obj_to_symbol(sg_symbol_inst* syminst, sg_value* obj) {
    AT(syminst->t == (sg_type*)sg_global_session()->bt->syminst, "Wrong type for symbol instance");
    SG_WRITE(2, "Binding object ");
    sg_writePtr(2, obj);
    SG_WRITE(2, " to symbol ");
    symtabPrintSymK(2, sg_global_session()->st, syminst->s);
    SG_WRITE(2, "\n");
    
    syminst->o = obj;
    sg_val_incref(obj);
}

void sg_release_symbol_inst(sg_symbol_inst* syminst) {
    AT(syminst->t == (sg_type*)sg_global_session()->bt->syminst, "Wrong type for symbol instance");
    free(syminst);
}

// ** name-value **

sg_name_val* sg_cast_as_name_val(sg_value* obj) {
    AT(obj->t == (sg_type*)sg_global_session()->bt->name, "Wrong type: cannot cast to name value");
    return (sg_name_val*)obj;
}

// ** network **

sg_network_val* sg_create_network() {
    sg_network_val* net = malloc(sizeof(sg_network_val));
    sg_init_val((sg_value*)net);
    net->t = (sg_type*)sg_global_session()->bt->network;
    
    net->rules = sg_create_list();
    
    return net;
}

sg_network_val* sg_cast_to_network(sg_value* val) {
    sg_network_val* net = (sg_network_val*)val;
    AT(net->t == (sg_type*)sg_global_session()->bt->network, "Unexpected type");
    
    return net;
}

void sg_network_add_rule(sg_network_val* net, sg_reaction_rule_val* rule) {
    AT(net->t == (sg_type*)sg_global_session()->bt->network, "Unexpected type");
    AT(rule->t == (sg_type*)sg_global_session()->bt->rxn_rule, "Unexpected type");
    
    sg_list_append(net->rules, (sg_value*)rule);
}

sg_reaction_rule_val* sg_create_reaction(sg_node_val* rxn, sg_node_val* rate) {
    sg_reaction_rule_val* rule = malloc(sizeof(sg_reaction_rule_val));
    sg_init_val((sg_value*)rule);
    rule->t = (sg_type*)sg_global_session()->bt->rxn_rule;
    
    rule->rxn = rxn;
    rule->rate = rate;
    
    return rule;
}

sg_reaction_rule_val* sg_cast_to_reaction_rule(sg_value* val) {
    sg_reaction_rule_val* rule = (sg_reaction_rule_val*)val;
    AT(rule->t == (sg_type*)sg_global_session()->bt->rxn_rule, "Unexpected type");
    
    return rule;
}

sg_node_val* sg_create_node(sg_node_val* a, sg_node_val* b, uint64 type, sg_name_val* name, sg_value* obj) {
    sg_node_val* node = malloc(sizeof(sg_node_val));
    
    if(a)
        AT(a->t == (sg_type*)sg_global_session()->bt->node, "Unexpected type");
    if(b)
        AT(b->t == (sg_type*)sg_global_session()->bt->node, "Unexpected type");
    AT(type < (uint64)PAST_NODE_TYPE_TERMINAL_SENTINEL);
    if(name)
        AT(name->t == (sg_type*)sg_global_session()->bt->name, "Unexpected type");
    
    sg_init_val((sg_value*)node);
    node->t = (sg_type*)sg_global_session()->bt->node;
    
    node->a = a;
    node->b = b;
    node->name = name;
    node->type = (parserExpType)type;
    node->obj = obj;
    
    return node;
}
